#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <time.h>
#include <string.h>

#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
#include <netinet/tcp.h>
#include <netdb.h>

#define  _PLATFORM_IS_LINUX_

#if defined(_PLATFORM_IS_LINUX_)
    #include <arpa/nameser.h>
    #include <resolv.h>
#endif

#include "charge_os.h"

void os_print(const char *str)
{
    printf("%s", str);
}

uint32_t os_get_time(void)
{
    return time(NULL);
}

void os_sleep_ms(uint32_t ms)
{
    usleep(1000 * ms);
}

void os_srandom(uint32_t seed)
{
    srandom(seed);
}

uint32_t os_random(void)
{
    return rand();
}

int32_t os_thread_create(const char *name, os_thread_t *thread, void *(*work_routine)(void *arg), void *par, \
                         os_task_priority_t prio, uint32_t stack_size)
{
    int32_t ret = -1;

    ret = pthread_create(thread, NULL, work_routine, par);
    if (ret != 0)
    {
        log_error("pthread_create error: %d\n", (int32_t)ret);
        return -1;
    }

    return 0;
}

int32_t os_mutex_create(os_mutex_t *mutex)
{
    return (0 == pthread_mutex_init(mutex, NULL)) ? 0 : -1;
}

int32_t os_mutex_destroy(os_mutex_t *mutex)
{
    return (0 == pthread_mutex_destroy(mutex)) ? 0 : -1;
}

int32_t os_mutex_lock(os_mutex_t *mutex, uint32_t timeout_ms)
{
    return (0 == pthread_mutex_lock(mutex)) ? 0 : -1;
}

int32_t os_mutex_unlock(os_mutex_t *mutex)
{
    return (0 == pthread_mutex_unlock(mutex)) ? 0 : -1;
}

int32_t os_persist_data_write(const void *data, uint32_t data_len)
{
    return user_write_data(data, data_len);
}

int32_t os_persist_data_read(void *data, uint32_t data_len)
{
    return user_read_data(data, data_len);
}

int32_t os_create_download_file(char *file_name)
{
    return user_create_download_file(file_name);
}

int32_t os_write_download_file_data(char *file_name, uint32_t offset, void *data, uint16_t data_len)
{
    return user_write_download_file_data(file_name, offset, data, data_len);
}

int32_t os_download_file_done(char *file_name, uint8_t state)
{
    return user_download_file_done(file_name, state);
}

static uint64_t _linux_get_time_ms(void)
{
    struct timeval tv = { 0 };
    uint64_t time_ms;

    gettimeofday(&tv, NULL);

    time_ms = tv.tv_sec * 1000 + tv.tv_usec / 1000;

    return time_ms;
}

static uint64_t _linux_time_left(uint64_t t_end, uint64_t t_now)
{
    uint64_t t_left;

    if (t_end > t_now)
    {
        t_left = t_end - t_now;
    }
    else
    {
        t_left = 0;
    }

    return t_left;
}

os_socket_t _tcp_open(const int8_t *host, uint16_t port)
{
    struct addrinfo hints;
    struct addrinfo *addrInfoList = NULL;
    struct addrinfo *cur = NULL;
    int32_t fd = 0;
    int32_t rc = 0;
    int8_t service[6];
    uint8_t dns_retry = 0;

    memset(&hints, 0, sizeof(hints));

    //log_debug("establish tcp connection with server(host='%s', port=[%u])\n", host, port);

    hints.ai_family = AF_INET; /* only IPv4 */
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    sprintf(service, "%u", port);

    while (dns_retry++ < 8)
    {
        rc = getaddrinfo(host, service, &hints, &addrInfoList);
        if (rc != 0)
        {
#if defined(_PLATFORM_IS_LINUX_)
            if (rc == EAI_AGAIN)
            {
                int32_t ret = 0;
                ret = res_init();
                log_error("getaddrinfo res_init, ret is %d, errno is %d\n", ret, errno);
            }
#endif
            log_error("getaddrinfo error[%d], res: %s, host: %s, port: %s\n", dns_retry, gai_strerror(rc), host, service);
            sleep(1);
            continue;
        }
        else
        {
            break;
        }
    }

    if (rc != 0)
    {
        log_error("getaddrinfo error(%d), host = '%s', port = [%d]\n", rc, host, port);
        return (os_socket_t)(-1);
    }

    for (cur = addrInfoList; cur != NULL; cur = cur->ai_next)
    {
        if (cur->ai_family != AF_INET)
        {
            log_error("socket type error\n");
            rc = -1;
            continue;
        }

        fd = socket(cur->ai_family, cur->ai_socktype, cur->ai_protocol);
        if (fd < 0)
        {
            log_error("create socket error\n");
            rc = -1;
            continue;
        }

        if (connect(fd, cur->ai_addr, cur->ai_addrlen) == 0)
        {
            rc = fd;
            break;
        }

        close(fd);
        //log_error("connect error\n");
        rc = -1;
    }

    if (-1 == rc)
    {
        //log_error("fail to establish tcp\n");
    }
    else
    {
        if (fd != -1)
        {
            fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
        }
        log_debug("success to establish tcp, fd=%d\n", rc);
    }
    freeaddrinfo(addrInfoList);

    return (os_socket_t)rc;
}

int32_t _tcp_close(os_socket_t fd)
{
    int32_t rc;

    /* Shutdown both send and receive operations. */
    rc = shutdown((int32_t) fd, 2);
    if (0 != rc)
    {
        log_error("shutdown error\n");
        return -1;
    }

    rc = close((int32_t) fd);
    if (0 != rc)
    {
        log_error("closesocket error\n");
        return -1;
    }

    return 0;
}

int32_t _tcp_write(os_socket_t fd, const int8_t *buf, uint32_t len, uint32_t timeout_ms)
{
    int32_t ret, tcp_fd;
    uint32_t len_sent;
    uint64_t t_end, t_left;
    fd_set sets;
    int32_t net_err = 0;

    t_end = _linux_get_time_ms() + timeout_ms;
    len_sent = 0;
    ret = 1; /* send one time if timeout_ms is value 0 */

    if (fd >= FD_SETSIZE)
    {
        return -1;
    }
    tcp_fd = (int32_t)fd;

    do
    {
        t_left = _linux_time_left(t_end, _linux_get_time_ms());

        if (0 != t_left)
        {
            struct timeval timeout;

            FD_ZERO(&sets);
            FD_SET(tcp_fd, &sets);

            timeout.tv_sec = t_left / 1000;
            timeout.tv_usec = (t_left % 1000) * 1000;

            ret = select(tcp_fd + 1, NULL, &sets, NULL, &timeout);
            if (ret > 0)
            {
                if (0 == FD_ISSET(tcp_fd, &sets))
                {
                    log_error("Should NOT arrive\n");
                    /* If timeout in next loop, it will not sent any data */
                    ret = 0;
                    continue;
                }
            }
            else if (0 == ret)
            {
                log_error("select-write timeout %d\n", tcp_fd);
                break;
            }
            else
            {
                if (EINTR == errno)
                {
                    log_error("EINTR be caught\n");
                    continue;
                }

                log_error("select-write fail, ret = select() = %d\n", ret);
                net_err = 1;
                break;
            }
        }

        if (ret > 0)
        {
            ret = send(tcp_fd, buf + len_sent, len - len_sent, 0);
            if (ret > 0)
            {
                len_sent += ret;
            }
            else if (0 == ret)
            {
                log_error("No data be sent\n");
            }
            else
            {
                if (EINTR == errno)
                {
                    log_error("EINTR be caught\n");
                    continue;
                }

                log_error("send fail, ret = send() = %d\n", ret);
                net_err = 1;
                break;
            }
        }
    } while (!net_err && (len_sent < len) && (_linux_time_left(t_end, _linux_get_time_ms()) > 0));

    if (net_err)
    {
        return -1;
    }
    else
    {
        return len_sent;
    }
}

int32_t _tcp_read(os_socket_t fd, int8_t *buf, uint32_t len, uint32_t timeout_ms)
{
    int32_t ret, err_code, tcp_fd;
    uint32_t len_recv;
    uint64_t t_end, t_left;
    fd_set sets;
    struct timeval timeout;

    t_end = _linux_get_time_ms() + timeout_ms;
    len_recv = 0;
    err_code = 0;

    if (fd >= FD_SETSIZE)
    {
        return -1;
    }

    tcp_fd = (int32_t)fd;

    do
    {
        t_left = _linux_time_left(t_end, _linux_get_time_ms());
        if (0 == t_left)
        {
            break;
        }
        FD_ZERO(&sets);
        FD_SET(tcp_fd, &sets);

        timeout.tv_sec = t_left / 1000;
        timeout.tv_usec = (t_left % 1000) * 1000;

        ret = select(tcp_fd + 1, &sets, NULL, NULL, &timeout);
        if (ret > 0)
        {
            ret = recv(tcp_fd, buf + len_recv, len - len_recv, 0);
            if (ret > 0)
            {
                len_recv += ret;
            }
            else if (0 == ret)
            {
                log_error("connection is closed by server\n");
                err_code = -1;
                break;
            }
            else
            {
                if (EINTR == errno)
                {
                    continue;
                }
                log_error("recv fail\n");
                err_code = -2;
                break;
            }
        }
        else if (0 == ret)
        {
            break;
        }
        else
        {
            if (EINTR == errno)
            {
                continue;
            }
            log_error("select-recv fail\n");
            err_code = -2;
            break;
        }
    } while ((len_recv < len));

    /* priority to return data bytes if any data be received from TCP connection. */
    /* It will get error code on next calling */
    return (0 != len_recv) ? len_recv : err_code;
}
